#pragma once

#include "Layer.h"

/// @file

/// @namespace pcpp
/// @brief The main namespace for the PcapPlusPlus lib
namespace pcpp
{
	/// @struct gre_basic_header
	/// Represents GRE basic protocol header (common for GREv0 and GREv1)
#pragma pack(push, 1)
	struct gre_basic_header
	{
#if (BYTE_ORDER == LITTLE_ENDIAN)
		/// Number of additional encapsulations which are permitted. 0 is the default value
		uint8_t recursionControl : 3,
		    /// Strict source routing bit (GRE v0 only)
		    strictSourceRouteBit : 1,
		    /// Set if sequence number exists
		    sequenceNumBit : 1,
		    /// Set if key exists
		    keyBit : 1,
		    /// Set if routing exists (GRE v0 only)
		    routingBit : 1,
		    /// Set if checksum exists (GRE v0 only)
		    checksumBit : 1;
#else
		/// Set if checksum exists (GRE v0 only)
		uint8_t checksumBit : 1,
		    /// Set if routing exists (GRE v0 only)
		    routingBit : 1,
		    /// Set if key exists
		    keyBit : 1,
		    /// Set if sequence number exists
		    sequenceNumBit : 1,
		    /// Strict source routing bit (GRE v0 only)
		    strictSourceRouteBit : 1,
		    /// Number of additional encapsulations which are permitted. 0 is the default value
		    recursionControl : 3;
#endif
#if (BYTE_ORDER == LITTLE_ENDIAN)
		/// GRE version - can be 0 or 1
		uint8_t version : 3,
		    /// Reserved
		    flags : 4,
		    /// Set if acknowledgment number is set (GRE v1 only)
		    ackSequenceNumBit : 1;
#else
		/// Set if acknowledgment number is set (GRE v1 only)
		uint8_t ackSequenceNumBit : 1,
		    /// Reserved
		    flags : 4,
		    /// GRE version - can be 0 or 1
		    version : 3;
#endif

		/// Protocol type of the next layer
		uint16_t protocol;
	};
#pragma pack(pop)
	static_assert(sizeof(gre_basic_header) == 4, "gre_basic_header size is not 4 bytes");

	/// @struct gre1_header
	/// Represents GREv1 protocol header
#pragma pack(push, 1)
	struct gre1_header : gre_basic_header
	{
		/// Size of the payload not including the GRE header
		uint16_t payloadLength;
		/// Contains the Peer's Call ID for the session to which this packet belongs
		uint16_t callID;
	};
#pragma pack(pop)
	static_assert(sizeof(gre1_header) == 8, "gre1_header size is not 8 bytes");

	/// @struct ppp_pptp_header
	/// Represents PPP layer that comes after GREv1 as part of PPTP protocol
#pragma pack(push, 1)
	struct ppp_pptp_header
	{
		/// Broadcast address
		uint8_t address;
		/// Control byte
		uint8_t control;
		/// Protocol type of the next layer (see PPP_* macros at PPPoELayer.h)
		uint16_t protocol;
	};
#pragma pack(pop)
	static_assert(sizeof(ppp_pptp_header) == 4, "ppp_pptp_header size is not 4 bytes");

	/// @class GreLayer
	/// Abstract base class for GRE layers (GREv0Layer and GREv1Layer). Cannot be instantiated and contains common logic
	/// for derived classes
	class GreLayer : public Layer
	{
	public:
		~GreLayer() override = default;

		/// A static method that determines the GRE version of GRE layer raw data by looking at the
		/// gre_basic_header#version field
		/// @param[in] greData GRE layer raw data
		/// @param[in] greDataLen Size of raw data
		/// @return ::GREv0 or ::GREv1 values if raw data is GREv0 or GREv1 (accordingly) or ::UnknownProtocol otherwise
		static ProtocolType getGREVersion(uint8_t* greData, size_t greDataLen);

		/// Get sequence number value if field exists in layer
		/// @param[out] seqNumber The returned sequence number value if exists in layer. Else remain unchanged
		/// @return True if sequence number field exists in layer. In this case seqNumber will be filled with the value.
		/// Or false if sequence number field doesn't exist in layer
		bool getSequenceNumber(uint32_t& seqNumber) const;

		/// Set sequence number value. If field already exists (gre_basic_header#sequenceNumBit is set) then only the
		/// new value is set. If field doesn't exist it will be added to the layer, gre_basic_header#sequenceNumBit will
		/// be set and the new value will be set
		/// @param[in] seqNumber The sequence number value to set
		/// @return True if managed to set the value successfully, or false otherwise (if couldn't extend the layer)
		bool setSequenceNumber(uint32_t seqNumber);

		/// Unset sequence number and remove it from the layer
		/// @return True if managed to unset successfully or false (and error log) if sequence number wasn't set in the
		/// first place or if didn't manage to remove it from the layer
		bool unsetSequenceNumber();

		// implement abstract methods

		/// Currently identifies the following next layers:
		///   IPv4Layer, IPv6Layer, VlanLayer, MplsLayer, PPP_PPTPLayer, EthLayer, EthDot3Layer
		/// Otherwise sets PayloadLayer
		void parseNextLayer() override;

		/// @return Size of GRE header (may change if optional fields are added or removed)
		size_t getHeaderLen() const override;

		OsiModelLayer getOsiModelLayer() const override
		{
			return OsiModelNetworkLayer;
		}

	protected:
		GreLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, ProtocolType protocol)
		    : Layer(data, dataLen, prevLayer, packet, protocol)
		{}

		GreLayer()
		{}

		enum GreField
		{
			GreChecksumOrRouting = 0,
			GreKey = 1,
			GreSeq = 2,
			GreAck = 3
		};

		uint8_t* getFieldValue(GreField field, bool returnOffsetEvenIfFieldMissing) const;

		void computeCalculateFieldsInner();
	};

	/// @class GREv0Layer
	/// Represents a GRE version 0 protocol. Limitation: currently this layer doesn't support GRE routing information
	/// parsing and editing. So if a GREv0 packet includes routing information it won't be parse correctly. I didn't add
	/// it because of lack of time, but if you need it please tell me and I'll add it
	class GREv0Layer : public GreLayer
	{
	public:
		/// A constructor that creates the layer from an existing packet raw data
		/// @param[in] data A pointer to the raw data
		/// @param[in] dataLen Size of the data in bytes
		/// @param[in] prevLayer A pointer to the previous layer
		/// @param[in] packet A pointer to the Packet instance where layer will be stored in
		GREv0Layer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : GreLayer(data, dataLen, prevLayer, packet, GREv0)
		{}

		/// A constructor that creates a new GREv0 header and allocates the data
		GREv0Layer();

		~GREv0Layer() override = default;

		/// Get a pointer to the basic GRE header containing only non-optional fields. Notice this points directly to
		/// the data, so every change will change the actual packet data. Also please notice that changing the set bits
		/// (gre_basic_header#strictSourceRouteBit, gre_basic_header#sequenceNumBit, gre_basic_header#keyBit,
		/// gre_basic_header#routingBit, gre_basic_header#checksumBit, gre_basic_header#ackSequenceNumBit) without using
		/// the proper set or unset methods (such as setChecksum(), unsetChecksum(), etc.) may result to wrong
		/// calculation of header length and really weird bugs. Please avoid doing so
		/// @return A pointer to the gre_basic_header
		gre_basic_header* getGreHeader() const
		{
			return reinterpret_cast<gre_basic_header*>(m_Data);
		}

		/// Get checksum value if field exists in layer
		/// @param[out] checksum The returned checksum value if exists in layer. Else remain unchanged
		/// @return True if checksum field exists in layer. In this case checksum parameter will be filled with the
		/// value. Or false if checksum field doesn't exist in layer
		bool getChecksum(uint16_t& checksum);

		/// Set checksum value. If checksum or offset fields already exist (gre_basic_header#checksumBit or
		/// gre_basic_header#routingBit are set) then only the new value is set. If both fields don't exist a new 4-byte
		/// value will be added to the layer, gre_basic_header#checksumBit will be set (gre_basic_header#routingBit will
		/// remain unset), the new checksum value will be set and offset value will be set to 0. The reason both fields
		/// are added is that GREv0 protocol states both of them or none of them should exist on packet (even if only
		/// one of the bits are set)
		/// @param[in] checksum The checksum value to set
		/// @return True if managed to set the value/s successfully, or false otherwise (if couldn't extend the layer)
		bool setChecksum(uint16_t checksum);

		/// Unset checksum and possibly remove it from the layer. It will be removed from the layer only if
		/// gre_basic_header#routingBit is not set as well. Otherwise checksum field will remain on packet with value of
		/// 0
		/// @return True if managed to unset successfully or false (and error log) if checksum wasn't set in the first
		/// place or if didn't manage to remove it from the layer
		bool unsetChecksum();

		/// Get offset value if field exists in layer. Notice there is no setOffset() method as GRE routing information
		/// isn't supported yet (see comment on class description)
		/// @param[out] offset The returned offset value if exists in layer. Else remain unchanged
		/// @return True if offset field exists in layer. In this case offset parameter will be filled with the value.
		/// Or false if offset field doesn't exist in layer
		bool getOffset(uint16_t& offset) const;

		/// Get key value if field exists in layer
		/// @param[out] key The returned key value if exists in layer. Else remain unchanged
		/// @return True if key field exists in layer. In this case key parameter will be filled with the value.
		/// Or false if key field doesn't exist in layer
		bool getKey(uint32_t& key) const;

		/// Set key value. If field already exists (gre_basic_header#keyBit is set) then only the new value is set.
		/// If field doesn't exist it will be added to the layer, gre_basic_header#keyBit will be set
		/// and the new value will be set
		/// @param[in] key The key value to set
		/// @return True if managed to set the value successfully, or false otherwise (if couldn't extend the layer)
		bool setKey(uint32_t key);

		/// Unset key and remove it from the layer
		/// @return True if managed to unset successfully or false (and error log) if key wasn't set in the first
		/// place or if didn't manage to remove it from the layer
		bool unsetKey();

		/// A static method that validates the input data
		/// @param[in] data The pointer to the beginning of a byte stream of an GREv0 layer
		/// @param[in] dataLen The length of the byte stream
		/// @return True if the data is valid and can represent an GREv0 layer
		static inline bool isDataValid(const uint8_t* data, size_t dataLen)
		{
			return data && dataLen >= sizeof(gre_basic_header);
		}

		// implement abstract methods

		/// Calculate the following fields:
		/// - gre_basic_header#protocol
		/// - GRE checksum field (if exists in packet)
		void computeCalculateFields() override;

		std::string toString() const override;
	};

	/// @class GREv1Layer
	/// Represents a GRE version 1 protocol
	class GREv1Layer : public GreLayer
	{
	public:
		/// A constructor that creates the layer from an existing packet raw data
		/// @param[in] data A pointer to the raw data
		/// @param[in] dataLen Size of the data in bytes
		/// @param[in] prevLayer A pointer to the previous layer
		/// @param[in] packet A pointer to the Packet instance where layer will be stored in
		GREv1Layer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : GreLayer(data, dataLen, prevLayer, packet, GREv1)
		{}

		/// A constructor that creates a new GREv1 header and allocates the data
		/// @param[in] callID The call ID to set
		explicit GREv1Layer(uint16_t callID);

		~GREv1Layer() override = default;

		/// Get a pointer to the basic GREv1 header containing all non-optional fields. Notice this points directly to
		/// the data, so every change will change the actual packet data. Also please notice that changing the set bits
		/// (gre_basic_header#strictSourceRouteBit, gre_basic_header#sequenceNumBit, gre_basic_header#keyBit,
		/// gre_basic_header#routingBit, gre_basic_header#checksumBit, gre_basic_header#ackSequenceNumBit) without using
		/// the proper set or unset methods (such as setAcknowledgmentNum(), unsetSequenceNumber(), etc.) may result to
		/// wrong calculation of header length or illegal GREv1 packet and to some really weird bugs. Please avoid doing
		/// so
		/// @return A pointer to the gre1_header
		gre1_header* getGreHeader() const
		{
			return reinterpret_cast<gre1_header*>(m_Data);
		}

		/// Get acknowledgment (ack) number value if field exists in layer
		/// @param[out] ackNum The returned ack number value if exists in layer. Else remain unchanged
		/// @return True if ack number field exists in layer. In this case ackNum will be filled with the value.
		/// Or false if ack number field doesn't exist in layer
		bool getAcknowledgmentNum(uint32_t& ackNum) const;

		/// Set acknowledgment (ack) number value. If field already exists (gre_basic_header#ackSequenceNumBit is set)
		/// then only the new value is set. If field doesn't exist it will be added to the layer,
		/// gre_basic_header#ackSequenceNumBit will be set and the new value will be set
		/// @param[in] ackNum The ack number value to set
		/// @return True if managed to set the value successfully, or false otherwise (if couldn't extend the layer)
		bool setAcknowledgmentNum(uint32_t ackNum);

		/// Unset acknowledgment (ack) number and remove it from the layer
		/// @return True if managed to unset successfully or false (and error log) if ack number wasn't set in the first
		/// place or if didn't manage to remove it from the layer
		bool unsetAcknowledgmentNum();

		/// A static method that validates the input data
		/// @param[in] data The pointer to the beginning of a byte stream of an GREv1 layer
		/// @param[in] dataLen The length of the byte stream
		/// @return True if the data is valid and can represent an GREv1 layer
		static inline bool isDataValid(const uint8_t* data, size_t dataLen)
		{
			return data && dataLen >= sizeof(gre1_header);
		}

		// implement abstract methods

		/// Calculate the following fields:
		/// - gre1_header#payloadLength
		/// - gre_basic_header#protocol
		void computeCalculateFields() override;

		std::string toString() const override;
	};

	/// @class PPP_PPTPLayer
	/// Represent a PPP (point-to-point) protocol header that comes after GREv1 header, as part of PPTP - Point-to-Point
	/// Tunneling Protocol
	class PPP_PPTPLayer : public Layer
	{
	public:
		/// A constructor that creates the layer from an existing packet raw data
		/// @param[in] data A pointer to the raw data (will be casted to @ref ppp_pptp_header)
		/// @param[in] dataLen Size of the data in bytes
		/// @param[in] prevLayer A pointer to the previous layer
		/// @param[in] packet A pointer to the Packet instance where layer will be stored in
		PPP_PPTPLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : Layer(data, dataLen, prevLayer, packet, PPP_PPTP)
		{}

		/// A constructor that allocates a new PPP-PPTP header
		/// @param[in] address Address field
		/// @param[in] control Control field
		PPP_PPTPLayer(uint8_t address, uint8_t control);

		~PPP_PPTPLayer() override = default;

		/// Get a pointer to the PPP-PPTP header. Notice this points directly to the data, so every change will change
		/// the actual packet data
		/// @return A pointer to the @ref ppp_pptp_header
		ppp_pptp_header* getPPP_PPTPHeader() const
		{
			return reinterpret_cast<ppp_pptp_header*>(m_Data);
		}

		/// A static method that validates the input data
		/// @param[in] data The pointer to the beginning of a byte stream of a PPP-PPTP packet
		/// @param[in] dataLen The length of the byte stream
		/// @return True if the data is valid and can represent a PPP-PPTP packet
		static inline bool isDataValid(const uint8_t* data, size_t dataLen);

		// implement abstract methods

		/// Currently identifies the following next layers: IPv4Layer, IPv6Layer. Otherwise sets PayloadLayer
		void parseNextLayer() override;

		/// @return The size of @ref ppp_pptp_header
		size_t getHeaderLen() const override
		{
			return sizeof(ppp_pptp_header);
		}

		/// Calculate the following fields:
		/// - ppp_pptp_header#protocol
		void computeCalculateFields() override;

		std::string toString() const override
		{
			return "PPP for PPTP Layer";
		}

		OsiModelLayer getOsiModelLayer() const override
		{
			return OsiModelSesionLayer;
		}
	};

	bool PPP_PPTPLayer::isDataValid(const uint8_t* data, size_t dataLen)
	{
		return data && dataLen >= sizeof(ppp_pptp_header);
	}

}  // namespace pcpp
